#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/boot.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#define F_CPU F_OSC
#include <util/delay.h>

#include "../firmware/uart.h"
#include "../firmware/usbn2mc.h"

//////////////////////////////////////////////////////////////////////////////

#define DEBUG2			0

#define O_SHOWLEDS		1

//////////////////////////////////////////////////////////////////////////////

#if DEBUG2
//#define L1_DebugWrite(s)		UARTWrite_P((PSTR)s)
#define L1_DebugWrite(s)		UARTWrite(s)
#define L1_DebugHex(n)			SendHex(n)
#else
#define L1_DebugWrite(s)		{}
#define L1_DebugHex(n)			{}
#endif

//////////////////////////////////////////////////////////////////////////////

// EEMEM wird bei aktuellen Versionen der avr-lib in eeprom.h definiert
// hier: definiere falls noch nicht bekannt ("alte" avr-libc)
#ifndef EEMEM
// alle Textstellen EEMEM im Quellcode durch __attribute__ ... ersetzen
#define EEMEM  __attribute__ ((section (".eeprom")))
#endif

//////////////////////////////////////////////////////////////////////////////

// __heap_start is declared in the linker script
//static uint8_t eeFooByte EEMEM = 1;

//////////////////////////////////////////////////////////////////////////////

/* pointer to the beginning of application code */
static void    (*avrupdate_jump_to_app) (void) = 0x0000;

//////////////////////////////////////////////////////////////////////////////

/* external interrupt from usbn9604 */
SIGNAL (SIG_INTERRUPT4)
{
	USBNInterrupt ();
}

//////////////////////////////////////////////////////////////////////////////

/* usbn2mc tiny needs this */
void USBNInterfaceRequests (DeviceRequest * req, EPInfo * ep)
{
}

/*  wait function */
static void wait_ms (int ms)
{
	while (ms--)
		_delay_ms (1);         // max delay is 262ms / 16 MHz = 16ms
}

/* pogramm a page into flash 
 *	@page = number of page
 *	@pageblock = pointer to data
 */

static void avrupdate_program_page (uint32_t page, uint8_t *pageblock)
{
//	L1_DebugWrite("flashing\r\n");
//	for (uint8_t i = 0; i < 16; i++)
//	{
//		for (uint8_t j = 0; j < 16; j++)
//			L1_DebugHex (pageblock [i*16+j]);
//		L1_DebugWrite("\r\n");
//	}
	
	uint8_t sreg = SREG;
	cli ();

	eeprom_busy_wait ();

	boot_page_erase (page);
	boot_spm_busy_wait ();		// Wait until the memory is erased.
	
	uint16_t *pageblock_w = (uint16_t *)pageblock;
	uint32_t page2 = page;
	uint8_t i = SPM_PAGESIZE / 2;
	do
	{
		boot_page_fill (page2, *pageblock_w++);
#if SPM_PAGESIZE <= 256
//		*(uint8_t *)&page2 += 2;
		asm ("inc %A0" "\n\t" "inc %A0" "\n\t" : "=r" (page2) : "0" (page2));
#else
		page2 += 2;
#endif
	} while (--i > 0);

	boot_page_write (page);		// Store buffer in flash page.
	boot_spm_busy_wait ();		// Wait until the memory is written.

	// Reenable RWW-section again. We need this if we want to jump back
	// to the application after bootloading.

	boot_rww_enable ();

	// Re-enable interrupts (if they were ever enabled).
	SREG = sreg;
}

//////////////////////////////////////////////////////////////////////////////

#define NONE	      0x00
#define STARTAPP      0x01
#define WRITEPAGE     0x02
#define GETVERSION    0x03
#define SETVERSION    0x04
#define STOPPROGMODE  0x05

//////////////////////////////////////////////////////////////////////////////

static volatile uint8_t  need_flash = 0;

static uint8_t  state = NONE;
static uint16_t page_addr;
static uint8_t  collect128 = 0;
static uint8_t  pageblock[SPM_PAGESIZE];

#define MAX_N	(SPM_PAGESIZE / 64)

//////////////////////////////////////////////////////////////////////////////

// start programm from application sector
static void avrupdate_start_app (void)
{
	// switch to run app mode
//	eeprom_write_byte (&eeFooByte, 0x00);

	L1_DebugWrite("\r\nbootloader is starting the APP ...\r\n\r\n");
	
//	if (collect128)
//	{
//		// there is some data, that is not written ...
//		//L1_DebugHex (page_addr);
//		uint32_t page_addr_w = page_addr * SPM_PAGESIZE;
//		avrupdate_program_page (page_addr_w, pageblock);
//		L1_DebugWrite("programm rest\r\n"); 
//	}

	USBNWrite (FIFORXC1, FLUSH);
	USBNWrite (MCNTRL, SRST);	// clear all usb registers

	cli ();
	MCUCR = (1 << IVCE);	// enable change of IVSEL
	MCUCR = 0;				// switch Interrupts vectors to Application Section

	avrupdate_jump_to_app ();	// Jump to application sector
}

static void avrupdate_cmd (uint8_t *buf)
{
	// check state first ist 
	switch (state)
	{
	case STOPPROGMODE:
		//eeprom_write_byte(&eeFooByte,0x00);
		//wait_ms(10);
		break;

	case WRITEPAGE:
//		L1_DebugWrite("page ");
//		L1_DebugHex (page_addr);
//		L1_DebugWrite(" ");
//		L1_DebugHex (collect128);
//		L1_DebugWrite("\r\n");

		// collect 64 byte packages
		{
		uint8_t *p = pageblock + 64 * collect128;
		for (uint8_t i = 64; i-- > 0; )
			*p++ = *buf++;
		}
		collect128++;

		if (collect128 == MAX_N)
		{
			// write page
			uint32_t page_addr_w = page_addr * SPM_PAGESIZE;
			avrupdate_program_page (page_addr_w, pageblock);
			collect128 = 0;
			state = NONE;
			
			pageblock [0] = 0x55;
			pageblock [1] = 0xAA;
			*(uint16_t *) &pageblock[2] = page_addr;
			USBNTransmitFIFO1 (4, pageblock);
		}
		break;

	default:
		L1_DebugWrite("default\r\n");
		state = buf[0];
		if (state == WRITEPAGE)
		{
			need_flash = 1;		// tell main to stay
			collect128 = 0;
			page_addr = *(uint16_t *) &buf[1];
		}

		if (state == STARTAPP)
		{
			avrupdate_start_app ();
			state = NONE;
		}
	}
}

// ********************************************************************
// Device descriptor
// ********************************************************************

//#define VID 0x1781
//#define PID 0x0c65

static const uint8_t avrupdateDevice[] =
{
	0x12,						// 18 length of device descriptor
	0x01,						// descriptor type = device descriptor 
	0x10, 0x01,					// version of usb spec. ( e.g. 1.1) 
	0x00,						// device class
	0x00,						// device subclass
	0x00,						// protocol code
	0x08,						// deep of ep0 fifo in byte (e.g. 8)
	0x81, 0x17,					// vendor id
	0x65, 0x0c,					// product id
	0x01, 0x00,					// revision id (e.g 1.02)
	0x00,						// index of manuf. string
	0x00,						// index of product string
	0x01,						// index of ser. number
	0x01						// number of configs
};

// ********************************************************************
// Configuration descriptor
// ********************************************************************

#define O_DO_RESPONSES		1

static const uint8_t avrupdateConf[] =
{
	0x09,						// 9 length of this descriptor
	0x02,						// descriptor type = configuration descriptor 
#if O_DO_RESPONSES
	7+
#endif
	9+9+7, 0x00,				// total length with first interface ... 
	0x01,						// number of interfaces
	0x01,						// number if this config. (arg for setconfig)
	0x00,						// string index for config
	0xA0,						// attrib for this configuration (bus powerded, remote wakup support)
	0x32,						// power for this configuration in * 2mA (e.g. 50mA)
	//InterfaceDescriptor
	0x09,						// 9 length of this descriptor
	0x04,						// descriptor type = interface descriptor 
	0x00,						// interface number 
	0x00,						// alternate setting for this interface 
#if O_DO_RESPONSES
	1+
#endif
	0x01,						// number endpoints without 0
	0x00,						// class code 
	0x00,						// sub-class code 
	0x00,						// protocoll code
	0x00,						// string index for interface
#if O_DO_RESPONSES
	//EP1 Descriptor
	0x07,						// 7 length of ep descriptor
	0x05,						// descriptor type= endpoint
	0x82,						// endpoint address (e.g. in ep2)
	0x02,						// transfer type (bulk)
	0x40, 0x00,					// fifo size
	0x00,						// polling intervall in ms
#endif
	//EP2 Descriptor
	0x07,						// 7 length of ep descriptor
	0x05,						// descriptor type= endpoint
	0x02,						// endpoint address (e.g. out ep2)
	0x02,						// transfer type (bulk)
	0x40, 0x00,					// fifo size
	0x00,						// polling intervall in ms
};

// HERE: decrypt ...
static const char *txt[] =
{
	"\x12\0032\0000\0000\0008\0001\0001\0000\0008\000", // Serial: "20081108"
//	"\x22\003E\000m\000b\000e\000d\000d\000e\000d\000P\000r\000o\000j\000e\000c\000t\000s\000",
//	"\x62\003O\000c\000t\000o\000p\000u\000s\000U\000S\000B\000 \000I\000n\000t\000e\000r\000f\000a\000c\000e\000 \000C\000o\000n\000v\000e\000r\000t\000e\000r\000 \000a\000n\000d\000 \000I\000/\000O\000 \000E\000x\000t\000e\000n\000s\000i\000o\000n\000",
};

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetDeviceDescriptor (void)
{
	return avrupdateDevice;
}

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetConfigDescriptor (uint8_t index)
{
	(void) index;
	return avrupdateConf;
}

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetString (uint8_t index)
{
	//EP0tx.Buf = (uint8_t *) FinalStringArray[index];
	return (const uint8_t *) txt [index-1];
}

//////////////////////////////////////////////////////////////////////////////

int main (void)
{
#if defined(MT_BOOTLOADER_ADDRESS)
#warning "compiling bootloader"
	cli ();		// probably no need ...
	MCUCR = (1 << IVCE);  // enable change of IVSEL
	MCUCR = (1 << IVSEL); // switch Interrupts vectors to Boot Section
#endif

#if O_SHOWLEDS
	DDRC = 0xFF;
	DDRB |= 0x80;
#endif

#if O_SHOWLEDS
	for (uint8_t i = 0; i < 10; i++)
	{
		PORTB ^= 0x80;
		wait_ms (100);	// timer is still slow
	}
#endif

	UARTInit();
	
	// if is no program in flash start bootloader, else start programm
//	uint8_t myByte = eeprom_read_byte (&eeFooByte);
	//SendHex(myByte);
	//if (pgm_read_byte(0) != 0xFF && myByte !=0x77)
	//if (pgm_read_byte(0) != 0xFF)
//	if (myByte == 0x00)
//	{
//		L1_DebugWrite("start app");
//		avrupdate_start_app ();
//	}

	// bootloader application starts here
	USBNCallbackFIFO_RX1 (avrupdate_cmd);

	USBNInitMC ();		// has sei ();

	// start usb chip and activate our 16MHz clock
	USBNStart ();
	
	L1_DebugWrite("\r\nbootloader is now active\r\n");
	wait_ms (100);

	if (pgm_read_byte(0) == 0xFF)
		need_flash = 1;
	uint8_t countdown = 10;		// can be reset by the upload
	while (countdown--)
	{
		if (need_flash)
			break;
#if O_SHOWLEDS
		PORTB |= 0x80;
		PORTC = 0xF0;			// keep USBN /CS high (0x80)
		wait_ms (500);
		PORTB &= 0x7F;
		PORTC = 0x87;
		wait_ms (500);
#endif
	}
	
	if (need_flash)				// loop forever, if no prog at 0 or download started
	{
		for (;;)
		{
#if O_SHOWLEDS
			PORTB |= 0x80;
			PORTC = 0xC0;		// keep USBN /CS high (0x80)
			wait_ms (500);
			PORTB &= 0x7F;
			PORTC = 0x81;
			wait_ms (500);
#endif
		}
	}

	avrupdate_start_app ();
	return 0;
}
